﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using log4net.Config;
using System.Threading;
using PeoplesJukebox.Core.Logging.Enums;

namespace PeoplesJukebox.Core.Logging
{
    /// <summary>
    /// Log4Net wrapper
    /// </summary>
    public static class Log
    {
        #region Private static fields

        private static ILog _logger;
        private static object syncLock = new object();

        #endregion

        #region Private static properties
        
        private static ILog Logger
        {
            get
            {
                if (_logger == null)
                {
                    lock (syncLock)
                    {
                        //use double-checked locking:
                        if (_logger == null)
                        {
                            XmlConfigurator.Configure();
                            _logger = LogManager.GetLogger("PeoplesJukebox.Log");
                        }
                    }
                }
                return _logger;
            }
        }

        #endregion

        #region Public static methods

        /// <summary>
        /// Writes a debug-level message to the log, using the configured sink
        /// </summary>
        /// <param name="message">Message to write</param>
        public static void Debug(string message)
        {
            QueueLog(LogLevel.Debug, message);
        }

        /// <summary>
        /// Writes a debug-level message to the log, using the configured sink
        /// </summary>
        /// <param name="messageFormat">String format to write</param>
        /// <param name="args">Format arguments</param>
        public static void Debug(string messageFormat, params object[] args)
        {
            QueueLog(LogLevel.Debug, messageFormat, args);
        }

        /// <summary>
        /// Writes an error-level message to the log, using the configured sink
        /// </summary>
        /// <param name="message">Message to write</param>
        public static void Error(string message)
        {
            QueueLog(LogLevel.Error, message);
        }

        /// <summary>
        /// Writes an error-level message to the log, using the configured sink
        /// </summary>
        /// <param name="messageFormat">String format to write</param>
        /// <param name="args">Format arguments</param>
        public static void Error(string messageFormat, params object[] args)
        {
            QueueLog(LogLevel.Error, messageFormat, args);
        }

        /// <summary>
        /// Writes a fatal-level message to the log, using the configured sink
        /// </summary>
        /// <param name="message">Message to write</param>
        public static void Fatal(string message)
        {
            QueueLog(LogLevel.Fatal, message);
        }

        /// <summary>
        /// Writes a fatal-level message to the log, using the configured sink
        /// </summary>
        /// <param name="messageFormat">String format to write</param>
        /// <param name="args">Format arguments</param>
        public static void Fatal(string messageFormat, params object[] args)
        {
            QueueLog(LogLevel.Fatal, messageFormat, args);
        }

        /// <summary>
        /// Writes an info-level message to the log, using the configured sink
        /// </summary>
        /// <param name="message">Message to write</param>
        public static void Info(string message)
        {
            QueueLog(LogLevel.Info, message);
        }

        /// <summary>
        /// Writes an info-level message to the log, using the configured sink
        /// </summary>
        /// <param name="messageFormat">String format to write</param>
        /// <param name="args">Format arguments</param>
        public static void Info(string messageFormat, params object[] args)
        {
            QueueLog(LogLevel.Info, messageFormat, args);
        }

        /// <summary>
        /// Writes a warning-level message to the log, using the configured sink
        /// </summary>
        /// <param name="message">Message to write</param>
        public static void Warn(string message)
        {
            QueueLog(LogLevel.Warn, message);
        }

        /// <summary>
        /// Writes a warning-level message to the log, using the configured sink
        /// </summary>
        /// <param name="messageFormat">String format to write</param>
        /// <param name="args">Format arguments</param>
        public static void Warn(string messageFormat, params object[] args)
        {
            QueueLog(LogLevel.Warn, messageFormat, args);
        }
        
        #endregion

        #region Private static methods
        
        private static void QueueLog(LogLevel level, string messageFormat, params object[] args)
        {
            //this is where the message to be logged is constructed;
            //enrichment (e.g. prefixing the calling type & method) can be added here:
            LogItem item = new LogItem();
            item.Level = level;
            item.Message = string.Format(messageFormat, args);

            WriteLogItem((object)item);
        }

        private static void WriteLogItem(object state)
        {
            LogItem item = state as LogItem;
            if (item != null)
            {
                try
                {
                    switch (item.Level)
                    {
                        case LogLevel.Debug:
                            Logger.Debug(item.Message);
                            break;
                        case LogLevel.Error:
                            Logger.Error(item.Message);
                            break;
                        case LogLevel.Fatal:
                            Logger.Fatal(item.Message);
                            break;
                        case LogLevel.Info:
                            Logger.Info(item.Message);
                            break;
                        case LogLevel.Warn:
                            Logger.Warn(item.Message);
                            break;
                    }
                }
                catch (Exception ex)
                {
                    //this may not be running on the main thread, so swallow the exception
                    //to prevent terminating the process
                }
            }
        }
        
        #endregion

        #region Private classes

        private class LogItem
        {
            private LogLevel _level;
            private string _message;

            public LogLevel Level
            {
                get { return _level; }
                set { _level = value; }
            }

            public string Message
            {
                get { return _message; }
                set { _message = value; }
            }
        }

        #endregion
    }
}
